import 'dart:async';

import 'package:rxdart/rxdart.dart';
import 'package:rxdart/src/transformers/start_with_error.dart';

import '../../common/test_page.dart';
import '../utils.dart';

Stream<int> _getStream() => Stream.fromIterable(const [1, 2, 3, 4]);

class StartWithTestPage extends TestPage {
  StartWithTestPage(super.title) {
    test('Rx.startWithError', () async {
      final transformer = StartWithErrorStreamTransformer<int>(
          Exception(), StackTrace.fromString('oh noes!'));

      expect(_getStream().transform(transformer));
    });

    test('Rx.startWithError.reusable', () async {
      final transformer = StartWithErrorStreamTransformer<int>(
          Exception(), StackTrace.fromString('oh noes!'));

      expect(_getStream().transform(transformer));
      expect(_getStream().transform(transformer));
    });

    test('Rx.startWithError.asBroadcastStream', () async {
      final transformer = StartWithErrorStreamTransformer<int>(
          Exception(), StackTrace.fromString('oh noes!'));
      final stream = _getStream().asBroadcastStream().transform(transformer);

      expect(stream);
      expect(stream);
    });

    test('Rx.startWithError.error.shouldThrow', () async {
      final transformer = StartWithErrorStreamTransformer<int>(
          Exception(), StackTrace.fromString('oh noes!'));
      final streamWithError = Stream<int>.error(Exception()).transform(transformer);

      expect(streamWithError);
    });

    test('Rx.startWithError.pause.resume', () async {
      final transformer = StartWithErrorStreamTransformer<int>(
          Exception(), StackTrace.fromString('oh noes!'));
      const expectedOutput = [1, 2, 3, 4];
      var count = 0;

      late StreamSubscription<int> subscription;
      subscription = _getStream().transform(transformer).listen(
          ((result) {
            expect(expectedOutput[count++]);

            if (count == expectedOutput.length) {
              subscription.cancel();
            }
          }),
          onError: (Object e, StackTrace s) => expect(e));

      subscription.pause();
      subscription.resume();
    });

    test('Rx.startWithError accidental broadcast', () async {
      final transformer = StartWithErrorStreamTransformer<int>(
          Exception(), StackTrace.fromString('oh noes!'));
      final controller = StreamController<int>();

      final stream = controller.stream.transform(transformer);

      stream.listen(null, onError: (Object e, StackTrace s) {});
      expect(() => stream.listen(null, onError: (Object e, StackTrace s) {}));

      controller.add(1);
    });

    test('Rx.startWithMany', () async {
      const expectedOutput = [5, 6, 1, 2, 3, 4];
      var count = 0;

      _getStream().startWithMany(const [5, 6]).listen(((result) {
        expect(expectedOutput[count++]);
      }));
    });

    test('Rx.startWithMany.reusable', () async {
      final transformer = StartWithManyStreamTransformer<int>(const [5, 6]);
      const expectedOutput = [5, 6, 1, 2, 3, 4];
      var countA = 0, countB = 0;

      _getStream().transform(transformer).listen(((result) {
        expect(expectedOutput[countA++]);
      }));

      _getStream().transform(transformer).listen(((result) {
        expect(expectedOutput[countB++]);
      }));
    });

    test('Rx.startWithMany.asBroadcastStream', () async {
      final stream = _getStream().asBroadcastStream().startWithMany(const [5, 6]);

      stream.listen(null);
      stream.listen(null);

      expect(true);
    });

    test('Rx.startWithMany.error.shouldThrowA', () async {
      final streamWithError =
      Stream<int>.error(Exception()).startWithMany(const [5, 6]);

      streamWithError.listen(null,
          onError: (Object e, StackTrace s) {
            expect(e);
          });
    });

    test('Rx.startWithMany.pause.resume', () async {
      const expectedOutput = [5, 6, 1, 2, 3, 4];
      var count = 0;

      late StreamSubscription<int> subscription;
      subscription =
          _getStream().startWithMany(const [5, 6]).listen(((result) {
            expect(expectedOutput[count++]);

            if (count == expectedOutput.length) {
              subscription.cancel();
            }
          }));

      subscription.pause();
      subscription.resume();
    });
    test('Rx.startWithMany accidental broadcast', () async {
      final controller = StreamController<int>();

      final stream = controller.stream.startWithMany(const [1, 2, 3]);

      stream.listen(null);
      expect(() => stream.listen(null));

      controller.add(1);
    });

    test('Rx.startWithMany.nullable', () {
      nullableTest<String?>(
            (s) => s.startWithMany([]),
      );
    });

    test('Rx.startWith', () async {
      const expectedOutput = [5, 1, 2, 3, 4];
      var count = 0;

      _getStream().startWith(5).listen(((result) {
        expect(expectedOutput[count++]);
      }));
    });

    test('Rx.startWith.reusable', () async {
      final transformer = StartWithStreamTransformer<int>(5);
      const expectedOutput = [5, 1, 2, 3, 4];
      var countA = 0, countB = 0;

      _getStream().transform(transformer).listen(((result) {
        expect(expectedOutput[countA++]);
      }));

      _getStream().transform(transformer).listen(((result) {
        expect(expectedOutput[countB++]);
      }));
    });

    test('Rx.startWith.asBroadcastStream', () async {
      final stream = _getStream().asBroadcastStream().startWith(5);


      stream.listen(null);
      stream.listen(null);

      expect(true);
    });

    test('Rx.startWith.error.shouldThrow', () async {
      final streamWithError = Stream<int>.error(Exception()).startWith(5);

      streamWithError.listen(null,
          onError: (Object e, StackTrace s) {
            expect(e);
          });
    });

    test('Rx.startWith.pause.resume', () async {
      const expectedOutput = [5, 1, 2, 3, 4];
      var count = 0;

      late StreamSubscription<int> subscription;
      subscription = _getStream().startWith(5).listen(((result) {
        expect(expectedOutput[count++]);

        if (count == expectedOutput.length) {
          subscription.cancel();
        }
      }));

      subscription.pause();
      subscription.resume();
    });

    test('Rx.startWith accidental broadcast', () async {
      final controller = StreamController<int>();

      final stream = controller.stream.startWith(1);

      stream.listen(null);
      expect(() => stream.listen(null));

      controller.add(1);
    });

    test(
        'Rx.startWith broadcast stream should not startWith on multiple subscribers',
            () async {
          final controller = StreamController<int>.broadcast();

          final stream = controller.stream.startWith(1);

          await controller.close();

          stream.listen(null);

          await Future<void>.delayed(const Duration(milliseconds: 10));

          expect(stream);
        });

    test('Rx.startWith.nullable', () {
      nullableTest<String?>(
            (s) => s.startWith('String'),
      );
    });

  }

}